package com.bieber.democamp.util;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.exceptions.JedisDataException;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Created by bieber on 2015/3/16.
 */
public class RedisClient {

    private static final String REDIS_CONFIG_FILE="redis_conf";
    private static final String CLASS_PATH_PREFIX="classpath:";
    private static final String FILE_PATH_PREFIX="file:";
    private static final Logger logger = LoggerFactory.getLogger(RedisClient.class);
    private static final String REDIS_CONNECT="redis_connect";
    private static  ShardedJedisPool JEDIS_POOL;
    static {
        String redisConfig = System.getProperty(REDIS_CONFIG_FILE);
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        try {
            Properties properties = loadProperties(redisConfig);
            Field[] fields = poolConfig.getClass().getDeclaredFields();
            for(Field field:fields){
                Method setMethod = getSetMethod(poolConfig.getClass(),field);
                if(properties.contains(field.getName())&&setMethod!=null&&setMethod.isAccessible()){
                    setMethod.invoke(poolConfig,CaseUtils.caseType(field.getType(),properties.get(field.getName()).toString()));
                }
            }
            Object redisConnect = properties.get(REDIS_CONNECT);
            if(redisConnect==null||StringUtils.isEmpty(redisConnect.toString())){
                throw new IllegalArgumentException("redis_connect must not empty");
            }
            String connectString = redisConnect.toString();
            String[] connects = connectString.split(",");
            List<JedisShardInfo> jedisShardInfoList = new ArrayList<JedisShardInfo>();
            for(String connect:connects){
                String[] splits = connect.split(":");
                JedisShardInfo jedisShardInfo = new JedisShardInfo(splits[0],splits[1]);
                jedisShardInfoList.add(jedisShardInfo);
            }
            JEDIS_POOL=new ShardedJedisPool(poolConfig,jedisShardInfoList);
        } catch (IOException e) {
            logger.error("failed to open redis config file",e);
        } catch (IllegalAccessException e) {
            logger.error("failed to config redis pool",e);
        } catch (InvocationTargetException e) {
            logger.error("failed to config redis pool", e);
        }
    }

    public static ShardedJedis getRedis(){
        return JEDIS_POOL.getResource();
    }

    public static void release(ShardedJedis jedis){
        JEDIS_POOL.returnResource(jedis);
    }

    public static void cacheMap(byte[] key,Map<byte[],byte[]> pairs,int expireSecond){
        ShardedJedis jedis = getRedis();
        try{
            jedis.hmset(key,pairs);
            jedis.expire(key,expireSecond);
        }finally {
            release(jedis);
        }
    }

    public static void refreshExpire(byte[] cacheKey,int expireSecond){
        ShardedJedis jedis = getRedis();
        try{
            jedis.expire(cacheKey,expireSecond);
        }finally {
            release(jedis);
        }
    }

    public static void putMapEntity(byte[] cacheKey,byte[] key,byte[] value){
        ShardedJedis jedis = getRedis();
        try{
            long len = jedis.hlen(cacheKey);
            if(len==0){
                Map<byte[],byte[]> map = new HashMap<byte[],byte[]>();
                map.put(key,value);
                jedis.hmset(cacheKey,map);
            }else{
                List<byte[]> values =jedis.hmget(cacheKey,key);
                if(values!=null&&values.size()==0){
                    jedis.hdel(cacheKey,values.get(0));
                }
                Map<byte[],byte[]> mapValues= jedis.hgetAll(cacheKey);
                mapValues.put(key,value);
                jedis.hmset(cacheKey,mapValues);
            }
        }finally {
            release(jedis);
        }
    }

    public static boolean exist(byte[] cacheKey){
        ShardedJedis jedis = getRedis();
        try{
             return jedis.hlen(cacheKey)>0;
        }finally {
            release(jedis);
        }
    }

    public static void removeCache(byte[] cacheKey){
        ShardedJedis jedis = getRedis();
        try{
            if(exist(cacheKey)){
                jedis.hdel(cacheKey);
            }
        }catch (JedisDataException exception){
            logger.debug("cache is deleted",exception);
        }finally {
            release(jedis);
        }
    }
    public static Object removeMapValue(byte[] cacheKey,byte[] key){
        ShardedJedis jedis = getRedis();
        try{
            Object object = getMapValue(cacheKey,key);
            jedis.hdel(cacheKey,key);
            return object;
        }finally {
            release(jedis);
        }
    }
    
    public static byte[] popList(byte[] key){
        ShardedJedis jedis = getRedis();
        try{
            byte[] bytes=jedis.lpop(key);
            return bytes;
        }finally {
            release(jedis);
        }
    }
    
    public static void pushList(byte[] key,byte[]... values){
        ShardedJedis jedis = getRedis();
        try{
            jedis.lpush(key,values);
        }finally {
            release(jedis);
        }
    }
    
    public static Object getMapValue(byte[] cacheKey,byte[] key){
        ShardedJedis jedis = getRedis();
        try{
            if(!jedis.exists(cacheKey)){
                return null;
            }
            List<byte[]> values = jedis.hmget(cacheKey, key);
            if(values!=null&&values.size()>0){
                return ObjectUtils.bytes2Object(values.get(0));
            }
            return null;
        } catch (ClassNotFoundException e) {
            logger.error("failed to read byte to object",e);
        } catch (IOException e) {
            logger.error("failed to read byte to object", e);
        } finally {
            release(jedis);
        }
        return null;
    }


    private static Method getSetMethod(Class<?> clazz,Field field)   {
        StringBuffer methodName = new StringBuffer("set");
        String fieldName = field.getName();
        methodName.append(fieldName.substring(0,1).toUpperCase()).append(fieldName.substring(1));
        try {
            return clazz.getMethod(methodName.toString(),field.getType());
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

    private static Properties loadProperties(String propertyFile) throws IOException {
        InputStream inputStream;
        if(propertyFile.startsWith(CLASS_PATH_PREFIX)){
            inputStream =  RedisClient.class.getClassLoader().getResourceAsStream(propertyFile.replace(CLASS_PATH_PREFIX, ""));
        }else if(propertyFile.startsWith(FILE_PATH_PREFIX)){
            inputStream = new FileInputStream(new File(propertyFile.replace(FILE_PATH_PREFIX,"")));
        }else{
            inputStream =  RedisClient.class.getClassLoader().getResourceAsStream(propertyFile);
        }
        Properties properties = new Properties();
        properties.load(inputStream);
        return properties;
    }
}
